﻿using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using handler = SilverQuery.EventCallback;

namespace SilverQuery
{
	/// <summary>
	/// Managed wrapper around a jQuery selected jQuery.
	/// </summary>
	public partial class jQuery : IEnumerable<HtmlElement>
	{
		private ScriptObject _jQuery;

		#region Constructors
		public jQuery(string selector)
		{
			_jQuery = Create(selector);
		}

		public jQuery(string selector, jQuery filter)
		{
			_jQuery = Create(selector, filter.NativeQuery);
		}

		public jQuery(string selector, HtmlElement filter)
		{
			_jQuery = Create(selector, filter);
		}

		public jQuery(HtmlObject htmlElement)
		{
			_jQuery = Create(htmlElement);
		}

		public jQuery(HtmlObject htmlElement, jQuery filter)
		{
			_jQuery = Create(htmlElement, filter.NativeQuery);
		}

		private jQuery(ScriptObject jQuery)
		{
			_jQuery = jQuery;
		}

		private static ScriptObject Create(params object[] args)
		{
			return HtmlPage.Window.CreateInstance("$", args);
		}
		#endregion

		internal ScriptObject NativeQuery
		{
			get { return _jQuery; }
		}

		public int length
		{
			get { return Convert.ToInt32(_jQuery.GetProperty("length")); }
		}

		/// <summary>
		/// Execute a callback method within the context of every matched element.
		/// </summary>
		/// <param name="callback"></param>
		/// <returns></returns>
		public jQuery each(Action<int, HtmlElement> callback)
		{
			((IEnumerable<HtmlElement>)this).each(callback);
			return this;
		}

		/// <summary>
		/// Access a single matched DOM element at a specified index in the matched jQuery.
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public HtmlElement get(int index)
		{
			return (HtmlElement)_jQuery.Invoke("get", index);
		}

		/// <summary>
		/// Get the current offset of the first matched element relative to the viewport.
		/// </summary>
		/// <returns></returns>
		public offset offset()
		{
			var obj = InvokeAndReturn<ScriptObject>("offset");
			var offset = new offset()
			{
				top = Convert.ToInt32(obj.GetProperty("top")),
				left = Convert.ToInt32(obj.GetProperty("left"))
			};
			return offset;
		}

		/// <summary>
		/// Get a set of elements containing the closest parent element that matches the specified selector, the starting element included.
		/// </summary>
		/// <param name="expr">An expression to filter the elements with.</param>
		/// <returns></returns>
		public jQuery closest(string expr)
		{
			return InvokeAndReturnNewQuery("closest", expr);
		}

		/// <summary>
		/// Binds a handler to an event (like click) for all current - and future - matched element. Can also bind custom events.
		/// </summary>
		/// <param name="type"></param>
		/// <param name="fn"></param>
		/// <returns></returns>
		/// <remarks>
		/// Possible event values: click, dblclick,  mousedown, mouseup, mousemove, mouseover, mouseout, keydown, keypress, keyup 
		/// </remarks>
		public jQuery live(String type, EventCallback fn)
		{
			return InvokeAndReturnSelf("live", type, SilverQueryBridge.GetScriptEventCallback(fn));
		}

		public jQuery die(string type, EventCallback fn)
		{
			return InvokeAndReturnSelf("live", type, SilverQueryBridge.GetScriptEventCallback(fn));
		}

		/// <summary>
		/// Load HTML from a remote file and inject it into the DOM.
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		public jQuery load(string url)
		{
			return this.load(url, null, null);
		}

		/// <summary>
		/// Load HTML from a remote file and inject it into the DOM.
		/// </summary>
		/// <param name="url"></param>
		/// <param name="data"></param>
		/// <param name="callback"></param>
		/// <remarks>A GET request will be performed by default - but if you pass in any extra parameters then a POST will occur.</remarks>
		/// <returns></returns>
		public jQuery load(string url, map data, AjaxCallback callback)
		{
			AjaxCallback wrapCall = (response, error) =>
				{
					if (error != null)
						this.html(response);

					callback(response, error);
				};

			if (data != null)
				Ajax.post(url, data, wrapCall);
			else
				Ajax.get(url, null, wrapCall);

			return this;
		}

		private jQuery InvokeAndReturnSelf(string function, params object[] args)
		{
			_jQuery = (ScriptObject)_jQuery.Invoke(function, args);
			return this;
		}

		private jQuery InvokeAndReturnNewQuery(string function, params object[] args)
		{
			ScriptObject result = (ScriptObject)_jQuery.Invoke(function, args);
			return new jQuery(result);
		}

		private int InvokeAndReturnInt(string function, params object[] args)
		{
			Double retVal = (double)_jQuery.Invoke(function, args);
			return Convert.ToInt32(retVal);
		}

		private T InvokeAndReturn<T>(string function, params object[] args)
		{
			object retVal = _jQuery.Invoke(function, args);
			return (T)retVal;
		}

		#region IEnumerable Implementation
		IEnumerator<HtmlElement> IEnumerable<HtmlElement>.GetEnumerator()
		{
			for (int i=0; i<this.length; i++)
			{
				yield return this.get(i);
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return ((IEnumerable<HtmlElement>)this).GetEnumerator();
		}
		#endregion
	}

	/// <summary>
	/// Custom type returned from the <see cref="jQuery.offset"/> method.
	/// </summary>
	public class offset
	{
		public int top { internal set; get; }
		public int left { get; internal set; }
	}
}
